// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.Client;
using System.Diagnostics;
using System.Threading;
using System.Data.SqlClient;
using System.Data;

namespace Microsoft.TeamFoundation.Migration.Toolkit.VC
{
    public abstract class VCMigrationEngine
    {
        protected VCMigrationEngine(VersionControlSession session, ChangeGroupingMananger manager)
        {
            if (session == null)
            {
                throw new ArgumentNullException("session");
            }

            if (manager == null)
            {
                throw new ArgumentNullException("manager");
            }

            m_session = session;
            m_manager = manager;

            m_retryLimit = MigrationConfiguration.Current.GetValue<int>("RetryLimit", 10);
            m_secondsToWait = MigrationConfiguration.Current.GetValue<int>("RetryDelaySeconds", 30);
            m_commentModifier = session.GetValue<string>("MigrationChangeCommentModifier", "(Migrated by TFS Migration Toolkit)");
        }

        public virtual void Run()
        {
            TraceManager.EnterMethod();

            using (TraceManager.StartLogicalOperation("Migrate"))
            {
                DemoteInprogressItems();

                MigrateEach();
            }

            TraceManager.ExitMethod();
        }

        protected virtual void UpdateConversionHistory(string sessionId, 
            string sourceId, 
            int tfsId, 
            DateTime utcWhen,
            string comment, 
            MigrationDirection direction)
        {
            using (SqlConnection conn = DataAccessManager.Current.GetSqlConnection())
            using (SqlCommand cmd = conn.CreateCommand())
            {
                conn.Open();

                cmd.CommandText = "prc_AddConversionHistory";
                cmd.CommandType = CommandType.StoredProcedure;

                cmd.Parameters.Add("@SessionId", SqlDbType.NVarChar).Value = sessionId;
                cmd.Parameters.Add("@SourceChangeId", SqlDbType.NVarChar).Value = sourceId;
                cmd.Parameters.Add("@TfsChangesetId", SqlDbType.Int).Value = tfsId;
                cmd.Parameters.Add("@UtcWhen", SqlDbType.DateTime).Value = utcWhen;
                cmd.Parameters.Add("@FromSource", SqlDbType.Bit).Value = (direction == MigrationDirection.SourceToTfs);
                cmd.Parameters.Add("@Comment", SqlDbType.NVarChar).Value = comment;

                int rows = cmd.ExecuteNonQuery();
                Debug.Assert(rows == 1);
            }
        }

        protected virtual void MigrateEach()
        {
            ChangeGrouping nextChange;

            while ((nextChange = Manager.Next()) != null)
            {
                int attempt = 0;
                bool tryAgain = true;

                while (tryAgain)
                {
                    Session.ThrowIfAborted();

                    tryAgain = false;

                    try
                    {
                        VersionControlEventArgs args = new VersionControlEventArgs(nextChange, nextChange.Name, 
                            (nextChange.Direction == MigrationDirection.SourceToTfs)?SystemType.Other:SystemType.Tfs);
                        Session.OnMigratingChangeStarting(args);

                        ProcessChangeGroup(nextChange);

                        Session.OnMigratingChangeComplete(args);
                    }
                    catch (VersionControlMigrationException me)
                    {
                        if (me.CanRetry)
                        {
                            if (attempt < RetryLimit)
                            {
                                attempt++;
                                tryAgain = true;
                                TraceManager.TraceInformation("Caught a retryable exception during migration.  Attempt {0} of {1} with {2} second delay.",
                                    attempt, RetryLimit, SecondsToWait);
                                TraceManager.TraceInformation(me.Message);

                                for (int i = 0; i < SecondsToWait; i++)
                                {
                                    Session.ThrowIfAborted();
                                    Thread.Sleep(1000);
                                };
                            }
                        }

                        if (!tryAgain)
                        {
                            throw;
                        }
                    }
                }
            }
        }

        protected virtual void DemoteInprogressItems()
        {
            Manager.DemoteInProgressActionsToPending();
        }

        protected abstract void ProcessChangeGroup(ChangeGrouping group);

        public virtual MigrationDirection Direction
        {
            get
            {
                return m_manager.Direction;
            }
        }

        public virtual VersionControlSession Session
        {
            get
            {
                return m_session;
            }

            protected set
            {
                m_session = value;
            }
        }

        public virtual ChangeGroupingMananger Manager
        {
            get
            {
                return m_manager;
            }

            protected set
            {
                m_manager = value;
            }
        }

        protected virtual string CommentModifier
        {
            get
            {
                return m_commentModifier;
            }
            set
            {
                m_commentModifier = value;
            }
        }

        protected virtual int RetryLimit
        {
            get
            {
                return m_retryLimit;
            }
            set
            {
                m_retryLimit = value;
            }
        }

        protected virtual int SecondsToWait
        {
            get
            {
                return m_secondsToWait;
            }
            set
            {
                m_secondsToWait = (value > 0) ? value : 1;
            }
        }

        int m_retryLimit;
        int m_secondsToWait;

        string m_commentModifier;

        VersionControlSession m_session;
        ChangeGroupingMananger m_manager;
    }
}
